package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.log4j.Logger;


/**
 * Prophesy from the Word of God (over 2500 years ago), which is starting to happen right now: 
 *
 * "Behold, I will make Jerusalem a cup of trembling unto all the people round about,
 *  when they shall be in the siege both against Judah and against Jerusalem.
 *  And in that day will I make Jerusalem a burdensome stone for all people: 
 *  all that burden themselves with it shall be cut in pieces, though all the 
 *  people of the earth be gathered together against it." (Zechariah 12:2-3)
 *  
 * "And it shall come to pass in that day, that I will seek to destroy 
 *  all the nations that come against Jerusalem." (Zechariah 12:9)
 * 
 * ==========
 * 
 * Jugile is a base class for all classes to provide all necessary convinience 
 * and common utility methods without using static calls to Util class.
 * 
 * @author jukka.rahkonen@iki.fi
 *
 */
public class Jugile {

	// ----- strings -----

	public static boolean empty(String str) {
		if (str == null || str.trim().length() == 0) return true;
		return false;
	}
	public static String nn(String str) {
		return nn((Object)str);
	}
	public static String nn(Object n) { 
		return nn(n, "");
	}
	public static String nn(Object n, String str) {
		if (n == null) return str;
		return n.toString();
	}
	
	public static String trim(String str) {
		if (str == null) return null;
		return str.trim();
	}
	
	public static String trim(String str, int len) {
		if (str == null) return null;
		if (len > str.length()) len = str.length();
		return str.substring(0,len);
	}
	
	public static String low(String str) {
		if (str == null) return null;
		if (str.length() == 0) return "";
		String initial = str.substring(0,1);
		return initial.toLowerCase() + str.substring(1);
	}
	public static String up(String str) {
		if (str == null) return null;
		if (str.length() == 0) return "";
		String initial = str.substring(0,1);
		return initial.toUpperCase() + str.substring(1);
	}
	
	public static String mult(String str, int factor) {
		String res = "";
		for (int i = 0; i < factor; i++) res += str;
		return res;
	}
	
	public static boolean eq(String s1, String s2) {
		if (s1 == null && s2 == null) return true;
		if (s1 == null || s2 == null) return false;
		return s1.equals(s2);
	}
	public static boolean eq(Object o1, Object o2) {
		if (o1 == null && o2 == null) return true;
		if (o1 == null || o2 == null) return false;
		return o1.equals(o2);
	}
	
	public static int parseIntSafe(String v) { return parseIntSafe(v,0); }
	public static int parseIntSafe(String v, int emptyValue) {
		if (empty(v)) return emptyValue;
		try { return Integer.parseInt(v.trim()); }
		catch (Exception e) { return emptyValue; }
	}
	
	public static long parseLongSafe(String v) { return parseLongSafe(v,0); }
	public static long parseLongSafe(String v, long emptyValue) {
		if (empty(v)) return emptyValue;
		try { return Long.parseLong(v.trim()); }
		catch (Exception e) { return emptyValue; }
	}

	public static float parseFloatSafe(String v) { return parseFloatSafe(v,0); } 
	public static float parseFloatSafe(String v, float emptyValue) {
		if (empty(v)) return emptyValue;
		try { return Float.parseFloat(v.trim()); }
		catch (Exception e) { return emptyValue; }
	}
	
	public static double parseDoubleSafe(String v) { return parseDoubleSafe(v,0); }
	public static double parseDoubleSafe(String v, double emptyValue) {
		if (empty(v)) return emptyValue;
		try { return Double.parseDouble(v.trim()); }
		catch (Exception e) { return emptyValue; }
	}
	
	public static boolean parseBoolSafe(String v) { return parseBoolSafe(v,false); }	
	public static boolean parseBoolSafe(String v, boolean emptyValue) {
		if (empty(v)) return emptyValue;
		if ("1".equals(v)) return true;
		if ("0".equals(v)) return false;
		try { return Boolean.parseBoolean(v); }
		catch (Exception e) { return emptyValue; }
	}

	public static Time parseTimeSafe(String v) { return parseTimeSafe(v,null); }	
	public static Time parseTimeSafe(String v, Time emptyValue) {
		if (empty(v)) return emptyValue;
		try { return new Time(v); }
		catch (Exception e) { return emptyValue; }
	}

	
	public static String join(List list) { return join(list, "toString", ";"); }
	public static String join(List list, String ch) { return join(list, "toString", ch); }
	public static String join(List list, String method, String str) { return join(list,method,str,null,null); }
	public static String join(List list, String method, String str, String q1, String q2) {
		String res = "";
		int count = 0;
		for (Object o : list) {
			if (count++ > 0) res += str;
			Proxy p = new Proxy(o);
			if (q1 != null) res += q1;
			res += escape(nn(p.call(method)),q2);
			if (q2 != null) res += q2;
		}
		return res;
	}
	public static String joinQuoted(List list) { return join(list,"toString",",","\"","\""); }
	
	public static List<String> split(String str) { return split(str,';'); }
	public static List<String> split(String str, char ch) {
		List<String> res = new ArrayList<String>();
		Buffer buf = new Buffer();
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == ch) {
				res.add(buf.toString());
				buf = new Buffer();
				continue;
			}
			if (c == '\\') {
				i++;
				if (i >= str.length()) continue;
				c = str.charAt(i);
			}
			buf.add(c);
		}
		res.add(buf.toString());
		return res;
	}
	
	public static List<String> list(String...items) {
		List<String> res = new ArrayList<String>();
		for (String item : items) res.add(item);
		return res;
	}
	
	public static String[] lines(String str) {
		return str.split("\r\n|\r|\n");
	}
	
	public static String escape(String str, String endtag) { 
		if (empty(endtag)) return escape(str,(char)0);
		return escape(str, endtag.charAt(0));
	}
	public static String escape(String str) { return escape(str, ';'); }
	public static String escape(String str, char ch) {
		Buffer buf = new Buffer();
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == ch) buf.add('\\');
			buf.add(c);
		}
		return buf.toString();
	}
		
	public static String replaceControls(String str) {
		Buffer buf = new Buffer();
		for (int i = 0; i < str.length(); i++) {
			char ch = str.charAt(i);
			if (ch == '\\') { buf.add("\\\\"); continue; }
			if (ch == '"')  { buf.add("\\\""); continue; }
			if (ch == '\b') { buf.add("\\b"); continue; }
			if (ch == '\f') { buf.add("\\f"); continue; }
			if (ch == '\n') { buf.add("\\n"); continue; }
			if (ch == '\r') { buf.add("\\r"); continue; }
			if (ch == '\t') { buf.add("\\t"); continue; }
			buf.add(ch);
		}
		return buf.toString();
	}
	
	public static int countIndent(String line) {
		int i = 0;
		while (i < line.length()) {
			if (!Character.isWhitespace(line.charAt(i))) return i;
			i++;
		}
		return i;
	}



	// ----- xml -----
	public static Node parseXml(String filename) {
		return SaxParser.parseXml(filename);
	}
	public static Node parseHtml(String filename) {
		return SaxParser.parseHtml(filename);
	}
	
	public static Node parseXmlString(String data) {
		return SaxParser.parseXmlString(data);
	}
	
	// ----- assert -----
	public static void chk(boolean expr) {
		if (!expr) fail("assertion check failed");
	}
	public static void chk(boolean expr, String msg) {
		if (!expr) fail("assertion check failed. " + msg);		
	}
	public static void chk(Object o) {
		if (o == null) fail("assertion null check failed");
	}
	public static void chk(Object o, String msg) {
		if (o == null) fail("assertion null check failed. " + msg);
	}
	
	// ----- time ------
	public static Time now(boolean real) {
		if (real) return new Time(new Date());
		return now();
	}
	public static Time now() {
		String now = Props.get("jugile.now");
		if (!empty(now)) {
			if (now.startsWith("+") || now.startsWith("-")) {
				if (now.startsWith("+")) now = now.substring(1);
				int hours = Integer.parseInt(now);
				Time t = new Time(new Date());
				t = t.plus(hours);
				return t;
			}
			return new Time(now);
		}
		return new Time(new Date());
	}
	public static String sdf(Time t) {
		if (t == null) return "";
		return t.toString();
	}
	public static String sdf(Time t, String format) {
		if (t == null) return "";
		return t.toString(format);
	}
	
	static Logger log = Logger.getLogger(Jugile.class);
	
	
	// ----- fail & log -----
	
	public static void fail(String msg) {
		throw new RuntimeException(msg);
	}
	
	public static void fail(Throwable e) {
		if (e instanceof RuntimeException) throw (RuntimeException)e;
		throw new RuntimeException(e);
	}
	
	public static void log(String msg) {
		log.debug(msg);
	}
	public static void log(Exception e, String msg) {
		log.debug(msg, e);
	}

	public static void print(String msg) {
		if (parseBoolSafe(Props.get("jugile.isProduction"))) {
			log.debug(msg);
		} else { 
			System.out.println(msg);
		}
	}

	// ----- resources and files -----
	public static InputStream getResource(String filename) {
		// first direct file
		File f = new File(filename);
		if (f.exists()) {
			try {
				return new FileInputStream(f);
			} catch(Exception e) {
				fail(e);
			}
		}
		if (filename.startsWith("./")) filename = filename.substring(2);
		if (filename.startsWith("/")) filename = filename.substring(1);
		Jugile foo = new Jugile();
		Class cl = foo.getClass();
		ClassLoader cloader = cl.getClassLoader();
		InputStream is = cloader.getResourceAsStream(filename);
		if (is != null) return is;
		fail("resource not found: " + filename);
		return null;
	}
	
	public static OutputStreamWriter writer(String fname) {
		try { return new OutputStreamWriter(new FileOutputStream(fname),"UTF-8"); }
		catch (Exception e) { fail(e); return null; }		
	}
	public static OutputStreamWriter zipWriter(String fname) {
		try { return new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(fname)), "UTF-8"); }
		catch (Exception e) { fail(e); return null; }
	}
	public static InputStream inputStream(String fname) {
		return getResource(fname);
	}
	public static InputStream zipInputStream(String fname) {
		try { return new GZIPInputStream(getResource(fname)); }
		catch (Exception e) { fail(e); return null; }		
	}
	public static InputStreamReader zipReader(String fname) {
		try { return new InputStreamReader(zipInputStream(fname),"UTF-8"); }
		catch (Exception e) { fail(e); return null; }		
	}
	public static InputStreamReader reader(String fname) {
		return new InputStreamReader(inputStream(fname));
	}	
	public static String getHostName() {
		try { return java.net.InetAddress.getLocalHost().getHostName(); }
		catch (Exception e) { return "--localhost--";}
	}
	
	public static void downloadFile(String url, String fname) {
		InputStream in = null;
		OutputStream out = null;
		try {
			URL u = new URL(url);
			URLConnection conn = u.openConnection();
			in = conn.getInputStream();
			out = new FileOutputStream(new File(fname));
			byte[] buffer = new byte[10*1024];
			int len = in.read(buffer);
			while (len != -1) {
			    out.write(buffer, 0, len);
			    len = in.read(buffer);
			}
		} catch (Exception e) {
			fail(e);
		} finally {
			if (out != null) try { out.close();} catch (Exception e) {}
			if (in != null) try { in.close();} catch (Exception e) {}
		}
	}

	 public static String downloadText(String url) {
		 try {
			 URL website = new URL(url);
			 URLConnection connection = website.openConnection();
			 BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			 StringBuilder response = new StringBuilder();
			 String inputLine;
	
			 while ((inputLine = in.readLine()) != null) 
				 response.append(inputLine + "\n");
	
			 in.close();
			 return response.toString();
		 } catch (Exception e) { fail(e); }
		 return null;
	 }
	 
	 public static String httpGet(String url) {
		 BufferedReader in = null;
		 OutputStreamWriter out = null;
		 try {
			 URL u = new URL(url);
			 HttpURLConnection uc = (HttpURLConnection)u.openConnection();
			 String method = "GET";
			 uc.setRequestMethod(method);	
			 if (uc.getResponseCode() != 200) {
				 fail("STATUS:" + uc.getResponseCode() + " " + uc.getResponseMessage());
			 }
			 //get result
			 in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
			 StringBuilder response = new StringBuilder();
			 String inputLine;
			 while ((inputLine = in.readLine()) != null) 
				 response.append(inputLine + "\n");
			 in.close(); in = null;
			 return response.toString();
		 } catch (Exception e) {
			 fail(e); return null;
		 }		
	 }


	 public static String readFile(String fname) {
		 InputStreamReader isr = null;
		 try {
			 isr = new InputStreamReader(inputStream(fname),"UTF8");
			 int ch = 0;
			 StringBuffer buf = new StringBuffer();
			 while ((ch = isr.read()) != -1) {
				 buf.append((char)ch);
			 }		
			 return buf.toString();
		 } catch (Exception e) { 
			 fail(e); return null; 
		 } finally {
			 try { isr.close(); } catch (Exception e) { fail(e); }
		 }		 
	 }
	 
	 public static void writeFile(String fname, String content) {
		 OutputStreamWriter out = Jugile.writer(fname);
		 try {
			 out.write(content);
			 out.close();
		 } catch (Exception e) { fail(e); }
	 }

	 public static void copyFile(File from, File to) {
		 try { doCopyFile(from,to); } catch (Exception e) { fail(e); }
	 }	
	 private static void doCopyFile(File sourceFile, File destFile) throws IOException {
		 if (!destFile.exists()) {
			 destFile.createNewFile();
		 }
		 FileInputStream fIn = null;
		 FileOutputStream fOut = null;
		 FileChannel source = null;
		 FileChannel destination = null;
		 try {
			 fIn = new FileInputStream(sourceFile);
			 source = fIn.getChannel();
			 fOut = new FileOutputStream(destFile);
			 destination = fOut.getChannel();
			 long transfered = 0;
			 long bytes = source.size();
			 while (transfered < bytes) {
				 transfered += destination.transferFrom(source, 0, source.size());
				 destination.position(transfered);
			 }
		 } finally {
			 if (source != null) source.close();
			 else if (fIn != null) fIn.close();
			 if (destination != null) destination.close();
			 else if (fOut != null) fOut.close();
		 }
	 }

	 
	// ------- classes --------
	/**
	 * Returns plain classname without package info.
	 */
	public static String getClassName(Class c) {
	    String cn = c.getName();
	    int first = cn.lastIndexOf ('.') + 1;
	    if (first > 0) cn = cn.substring(first);
	    return cn;
	}	
	public static String getClassName(Object o) {
		if (o == null) return null;
		return getClassName(o.getClass());
	}

	public static Object instantiateObject(String name) {
		Object c;
        Class aClass = getClassByName(name);
	    try { c = aClass.newInstance();
	    } catch (Exception e) { throw new RuntimeException(e); }
		return c;
	}
	
	public static Object instantiateObjectSafe(String name) {
		Object c;
		Class clazz = null;
		ClassLoader classLoader = Jugile.class.getClassLoader();
	    try { 
	    	clazz = classLoader.loadClass(name);
	    } catch (Exception e) {
	    	return null; // safe
	    }
        Class aClass = getClassByName(name);
	    try { 
	    	c = aClass.newInstance();
	    } catch (Exception e) { 
	    	return null; // safe
	    }
		return c;
	}
	
	public static Class getClassByName(String name) {
		ClassLoader classLoader = Jugile.class.getClassLoader();
	    try { return classLoader.loadClass(name);
	    } catch (Exception e) { throw new RuntimeException(e); }
	}
	
	public static String getObjectPath(Object o) {
		String clname = o.getClass().getName();
		clname = clname.substring(0, clname.lastIndexOf('.'));
		clname = clname.replaceAll("\\.", "/");
		return clname;
	}
	
	public static boolean isEnumType(Class cl) {
		for (Class i : cl.getInterfaces()) {
			if (i == org.jugile.util.EnumType.class) return true;
		}
		return false;
	}

	
	// ----- utf8 -----
	// TODO: optimize this, is there nicer way ?
	public static String getUtf8(byte[] data) {
		if (data == null) return null;
		ByteArrayInputStream bis = new ByteArrayInputStream(data);
		try {
			InputStreamReader isr = new InputStreamReader(bis, "UTF-8");
			int ch;
			StringBuffer buf = new StringBuffer();
			while ((ch = isr.read()) >= 0) buf.append((char)ch);
			return buf.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	// ----- id & etc -----
	public static String generateId(long l) {
		String chars = "0123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
		long mod = l % chars.length();
		if (l-mod == 0) return ""+chars.charAt((int)mod);
		return generateId((l-mod)/chars.length()) + chars.charAt((int)mod);
	}
	
	public static String hex(int i) {
		if (i < 0) i = 0;
		String chars = "0123456789ABCDEF";
		int mod = i % chars.length();
		if (i-mod == 0) return ""+chars.charAt(mod);
		return hex((i-mod)/chars.length()) + chars.charAt(mod);
	}
	public static String hex(int i, int len) {
		String hex = hex(i);
		while (hex.length() < len) hex = "0"+hex;
		return hex;
	}

	
	// ----- domain -----
	public static IDomain getDomainInstance() {
		String clname = Props.getDomainClassName();
		if (empty(clname)) return null;
		return (IDomain)Proxy.staticCall(clname, "getDomain");
	}
	
	public static void saveObject(Object o, String fname) {
		try {
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fname));
			oos.writeObject(o);
			oos.close();
		} catch (Exception e) { fail(e); }
	}

	public static Object loadObject(String fname) {
		try {
			ObjectInputStream ois = new ObjectInputStream(getResource(fname));
			Object o = ois.readObject();
			ois.close();
			return o;
		} catch (Exception e) { fail(e); return null; }
	}

	// ----- memory ------
	public static long showmem() {
		long mem0 = Runtime.getRuntime().totalMemory() -
	      Runtime.getRuntime().freeMemory();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    System.gc(); System.gc(); System.gc(); System.gc();
	    mem0 = Runtime.getRuntime().totalMemory() -
	      Runtime.getRuntime().freeMemory();
	    return mem0;
	}

}
